#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>         /* offsetof macro */
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>       /* S_IRWXU */
#include "job_schd.h"
#include "ipc_def.h"

char *_getlogin(void)
{
    char *login_name;
    /* can be overwritten, but never free it. */
    if (!(login_name = getlogin())) {
        fprintf(stderr, "calling getlogin failed: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    return login_name;
}

void _getusername(uid_t uid, char *buf, unsigned int buf_size)
{
    struct passwd *pwd;
    unsigned int size;
    if ((pwd = getpwuid(uid)) == NULL) {
        return;
    }
    size = MIN(strlen(pwd->pw_name), buf_size);
    memcpy(buf, pwd->pw_name, size);
    /* whatever */
    buf[size - 1] = '\0';
}
/*
 * Applications wishing to check for error situations should set
 * errno to 0 before calling getpwuid(). If getpwuid() returns a
 * null pointer and errno is set to non-zero, an error occured.
 */
int check_permission(char *fpath, uid_t euid)
{
    struct passwd *pwd;
    struct stat   sta;

    if ((pwd = getpwuid(euid)) == NULL) {
        return -1;
    }
    if (stat(fpath, &sta) < 0) {
        return -2;
    }
    if (pwd->pw_uid == ROOT_UID) {
        return 0;
    }
    else if (sta.st_uid == pwd->pw_uid &&
            (sta.st_mode & S_IXUSR)) {
        return 0;
    }
    else if (sta.st_gid == pwd->pw_gid &&
            (sta.st_mode & S_IXGRP)) {
        return 0;
    }
    else if (sta.st_mode & S_IXOTH) {
        return 0;
    }
    else {
        return -3;
    }
}

int init_lisn(void)
{
    int sockfd;
    int size;
    int err;
    int rval;
    struct sockaddr_un sa_un;

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
    unlink(SERVER_SOCKET_NAME);

    memset(&sa_un, 0, sizeof(struct sockaddr_un));
    sa_un.sun_family = AF_UNIX;
    strcpy(sa_un.sun_path, SERVER_SOCKET_NAME);
    size = offsetof(struct sockaddr_un, sun_path) + strlen(sa_un.sun_path);

    if (bind(sockfd, (struct sockaddr *)&sa_un, size) < 0) {
        rval = -2;
        goto err_out;
    }
    if (listen(sockfd, BACK_LOG_SIZE) < 0) {
        rval = -3;
        goto err_out;
    }
    return sockfd;
err_out:
    err = errno;
    close(sockfd);
    errno = err;
    return rval;
}

int init_acpt(int lisnfd, uid_t *uid_p)
{
    int cli_sockfd;
    int size;
    int err;
    int rval;
    time_t staletime;
    struct sockaddr_un sa_un;
    struct stat stat_buf;

    size = sizeof(struct sockaddr_un);
    if ((cli_sockfd = accept(lisnfd, (struct sockaddr *)&sa_un, (socklen_t *)&size)) < 0) {
        return -1;
    }
    size -= offsetof(struct sockaddr_un, sun_path);
    sa_un.sun_path[size] = '\0';
    if (stat(sa_un.sun_path, &stat_buf) < 0) {
        rval = -2;
        goto err_out;
    }
#ifdef S_ISSOCK     /* not defined for SVR4 */
    if (S_ISSOCK(stat_buf.st_mode) == 0) {
        rval = -3;
        goto err_out;
    }
#endif
    if ((stat_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
        (stat_buf.st_mode & S_IRWXU) != S_IRWXU) {
        rval = -4;
        goto err_out;
    }
    /*
    staletime = time(NULL) - STALE_TIME;
    if (stat_buf.st_atime < staletime ||
        stat_buf.st_ctime < staletime ||
        stat_buf.st_mtime < staletime) {
        rval = -5;
        goto err_out;
    }*/
    if (uid_p != NULL) {
        *uid_p = stat_buf.st_uid;
    }
    unlink(sa_un.sun_path);
    return cli_sockfd;
err_out:
    err = errno;
    close(cli_sockfd);
    errno = err;
    return rval;
}

/* used by enq and deq */
int init_conn(void)
{
#define CLI_PATH "/var/tmp/"
#define CLI_PERM S_IRWXU
    int sockfd;
    int size;
    int rval;
    int err;
    struct sockaddr_un sa_un;

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
    memset(&sa_un, 0, sizeof(struct sockaddr_un));
    sa_un.sun_family = AF_UNIX;
    sprintf(sa_un.sun_path, "%s%05d", CLI_PATH, getpid());
    size = offsetof(struct sockaddr_un, sun_path) + strlen(sa_un.sun_path);

    unlink(sa_un.sun_path);
    if (bind(sockfd, (struct sockaddr *)&sa_un, size) < 0) {
        rval = -2;
        goto err_out;
    }
    if (chmod(sa_un.sun_path, CLI_PERM) < 0) {
        rval = -3;
        goto err_out;
    }

    memset(&sa_un, 0, sizeof(struct sockaddr_un));
    sa_un.sun_family = AF_UNIX;
    strcpy(sa_un.sun_path, SERVER_SOCKET_NAME);
    size = offsetof(struct sockaddr_un, sun_path) + strlen(sa_un.sun_path);
    if (connect(sockfd, (struct sockaddr *)&sa_un, size) < 0) {
        rval = -4;
        goto err_out;
    }
    return (sockfd);

err_out:
    err = errno;
    close(sockfd);
    errno = err;
    return rval;
}

/*
 * Two sides are on the same machine.
 * No endian tranformation is needed.
 * I am on a x86 compatible machine which
 * should be little-endian.
 * Endian independent code should be considered.
 */
void send_item(int sockfd, unsigned char tag_type, unsigned char tag_len, char *tag_value)
{
    if (!tag_value && send(sockfd, &tag_type, sizeof(unsigned char), 0) < 0) {
        goto err_out;
    }
    else if (send(sockfd, &tag_type, sizeof(unsigned char), 0) < 0 ||
            send(sockfd, &tag_len, sizeof(unsigned char), 0) < 0 ||
            send(sockfd, tag_value, tag_len, 0) < 0) {
        goto err_out;
    }
    return;
err_out:
    fprintf(stderr, "calling send failed: %s.\n", strerror(errno));
    close(sockfd);
    exit(EXIT_FAILURE);
}

int recv_to_buf(int sockfd, unsigned char *buf, unsigned int size)
{
    if (recv(sockfd, buf, size, MSG_WAITALL) < 0) {
        return -1;
    }
    return 0;
}

int recv_tag_from_buf(struct tag_struct *tag, unsigned char *base, unsigned char **buf_p)
{
    unsigned char *p = *buf_p;
    tag->tag_type = *p;
    tag->tag_len = *(p + 1);
    p += 2;
    if (p - base + tag->tag_len > SOCK_BUF_SIZE) {
        return -1;
    }
    memcpy(tag->tag_value, p, tag->tag_len);
    *buf_p += (2 + tag->tag_len);
    return 0;
}

void print_job_info(int outfd, struct JobInfo *job_info)
{
    const char *state_to_string[] = {
        "STOPPED", "READY", "RUNNING", "DONE",
    };
#ifdef _GNU_SOURCE
    dprintf(outfd, "%4s%6s%6s%32s%4s%4s%8s",
            "jid",
            "pid",
            "euid",
            "username",
            "init_prior",
            "curr_prior",
            "state");
    dprintf(outfd, "%4u%6lu%6lu%32s%4u%4u%8s",
            job_info->jid,
            job_info->pid,
            job_info->euid,
            job_info->username,
            job_info->init_prior,
            job_info->curr_prior,
            state_to_string[job_info->state]);
    dprintf(outfd, "time created : %s", ctime(&job_info->crea_time.tv_sec));
    dprintf(outfd, "time executed: %s", ctime(&job_info->exec_time.tv_sec));
    dprintf(outfd, "time waiting : %s", ctime(&job_info->wait_time.tv_sec));
#else
    FILE *fout;

    fout = fdopen(outfd, "w");
    fprintf(fout, "%4s%6s%6s%32s%4s%4s%8s",
            "jid",
            "pid",
            "euid",
            "username",
            "init_prior",
            "curr_prior",
            "state");
    fprintf(fout, "%4u%6lu%6lu%32s%4u%4u%8s",
            job_info->jid,
            job_info->pid,
            job_info->euid,
            job_info->username,
            job_info->init_prior,
            job_info->curr_prior,
            state_to_string[job_info->state]);
    fprintf(fout, "time created : %s", ctime(&job_info->crea_time.tv_sec));
    fprintf(fout, "time executed: %s", ctime(&job_info->exec_time.tv_sec));
    fprintf(fout, "time waiting : %s", ctime(&job_info->wait_time.tv_sec));
#endif
}
